// 
//  Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as
//  published by the Free Software Foundation, either version 3 of the
//  License, or (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
// 
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// 


using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Linq;
using Galaxium.Protocol.Xmpp.Library.Core;
using Galaxium.Protocol.Xmpp.Library.Utility;

// TODO: Delayed (offline) sending for all messages and some "set" Iq's 

// TODO: implement SqLite storage

namespace Galaxium.Protocol.Xmpp.Library.Messaging
{
	public delegate void MessageHandler (int message_id);
	
	public class MessageManager
	{
		private class ThreadInfo {
			public DateTime Last { get; set; }
			public int MessageCount { get; set; }
			public int UnreadCount { get; set; }
		}
		
		#region Variables
		
		private Client _client;

		private List<MessageInfo> _messages;
		private Dictionary<string, ThreadInfo> _threads;
		private Dictionary<string, List<int>> _thread_messages;
		
		private int _id = 0;

		#endregion
		
		public int UnreadCount { get; private set; }

		public event MessageHandler NewMessageEvent = delegate {};
		public event MessageHandler MessageStateChangedEvent = delegate {};
		public event MessageHandler MessageShownEvent = delegate {}; // provided to be able to remove notification
		
		internal MessageManager (Client client)
		{
			_client = client;
			_messages = new List<MessageInfo> ();
			_threads = new Dictionary<string, ThreadInfo> ();
			_thread_messages = new Dictionary<string, List<int>> ();
		}

		internal void HandleMessage (XmlMessage msg)
		{
			if (String.IsNullOrEmpty (msg.Body) && String.IsNullOrEmpty (msg.Subject)) return;
			Store (new MessageInfo (msg, false));
		}
		
		internal void HandleError (XmlMessage msg)
		{
			// TODO: finish error handling
		}

		public void Send (string subject, string body,
		                  string thread, string parent_thread,
		                  JabberID[] recipients)
		{
			if (thread == null)
				thread = Uuid.GenerateRandom ();
			
			var msg = new XmlMessage ();
			msg.ID = "n" + (++ _id);
			msg.AppendThread (thread);
			msg.AppendSubject (subject);
			msg.AppendBody (body);

		//	if (!parent_thread.IsNullOrEmpty ())
		//		msg.ParentThread = parent_thread;  TODO: implement parent threads

			foreach (JabberID jid in recipients) {
				msg.To = jid;
				_client.Send (msg);
				Store (new MessageInfo (msg, true));
			}
		}

		public void Store (MessageInfo msginfo)
		{
			lock (this) {
				int top = _messages.Count;
				_messages.Add (msginfo);
				if (!_thread_messages.ContainsKey (msginfo.Thread)) {
					_threads [msginfo.Thread] = new ThreadInfo ();
					_thread_messages [msginfo.Thread] = new List<int> ();
				}
				if (msginfo.IsUnread) {
					_threads [msginfo.Thread].UnreadCount ++;
					UnreadCount ++;
				}
				_thread_messages [msginfo.Thread].Add (top);
				_threads [msginfo.Thread].Last = msginfo.Time;
				_threads [msginfo.Thread].MessageCount ++;
				NewMessageEvent (top);
			}
		}

		public string[] GetThreads ()
		{
			lock (this) {
				return _thread_messages.Keys.ToArray ();
			}
		}

		public int[] GetMessageIds (string thread_id)
		{
			lock (this) {
				return _thread_messages [thread_id].ToArray ();
			}
		}
		
		public MessageHead[] GetMessagesInThread (string thread_id)
		{
			lock (this) {
				var messages = new List<MessageHead> ();
				foreach (int i in _thread_messages [thread_id])
					messages.Add (GetMessage (i));
				return messages.ToArray ();
			}
		}

		public MessageHead[] GetThreadHeads ()
		{
			lock (this) {
				var list = new List<MessageHead> ();
				foreach (var thread in _thread_messages.Values)
					list.Add (GetMessage (thread.Last ()));
				return list.ToArray ();
			}
		}
		
		public MessageHead GetMessage (int id)
		{
			MessageShownEvent (id);
			var msginfo = _messages [id];
			var msghead = new MessageHead ();
			msghead.Id = id;
			msghead.Time = msginfo.Time;
			msghead.Thread = msginfo.Thread;
			msghead.Subject = msginfo.Subject;
			msghead.Contact = msginfo.Contact;
			msghead.IsOutgoing = msginfo.IsOutgoing;
			msghead.IsUnread = msginfo.IsUnread;
			return msghead;
		}

		public int GetThreadUnreadCount (string thread_id)
		{
			return _threads [thread_id].UnreadCount;
		}

		public int GetThreadMessageCount (string thread_id)
		{
			return _threads [thread_id].MessageCount;
		}
		
		public MessageInfo GetMessageContent (int id)
		{
			MessageShownEvent (id);
			var msg = _messages [id];
			if (msg.IsUnread) {
				msg.IsUnread = false;
				_threads [msg.Thread].UnreadCount --;
				UnreadCount --;
				MessageStateChangedEvent (id);
			}
			return _messages [id];
		}
	}
}
